package app.species.util;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferStrategy;

import javax.swing.JFrame;

import ec.util.MersenneTwisterFast;

import sim.engine.SimState;
import sim.engine.Steppable;
import sim.engine.Stoppable;
import app.species.Creature;
import app.species.SpeciesSim;
import app.species.ANN.AdamBrain;
import app.species.ANN.Axon;
import app.species.ANN.AxonChromosome;
import app.species.ANN.Brain;
import app.species.ANN.InputNeuron;
import app.species.ANN.Neuron;
import app.species.ANN.SomaChromosome;
import app.species.ANN.SomaGene;

public class VisualizeBrain extends JFrame implements Steppable, WindowListener, MouseMotionListener, MouseListener {

	Brain brain;
	public Stoppable stoppable;

	myNeurons[] neurons;
	myAxons[] axons;

	private double x_angel, y_angel, z_angel;

	private double maxX, maxY;

	public VisualizeBrain(Brain brain, SpeciesSim sim) {
		if (sim != null)
			stoppable = sim.schedule.scheduleRepeating(this);

		this.brain = brain;
		// this.setUndecorated(true);
		this.setSize(800, 600);
		this.setVisible(true);
		addWindowListener(this);
		addMouseMotionListener(this);
		addMouseListener(this);
		this.createBufferStrategy(3);

		preprocessingBrain(brain);
	}

	public static void main(String[] args) {
		VisualizeBrain v = (new VisualizeBrain(AdamBrain.makeBrain(3, 4, new MersenneTwisterFast()), null));
		while (v.isVisible())
			v.step(null);
		System.exit(0);
	}

	private void preprocessingBrain(Brain brain) {
		Neuron[] n = brain.getNeurons();
		Axon[] a = brain.getAxons();
		AxonChromosome aChrome = brain.getAxonChromosome();
		SomaChromosome sChrome = brain.getSomaChromosome();

		// initialize wrapper
		neurons = new myNeurons[n.length];
		axons = new myAxons[a.length];

		PointGenerator pGen = new PointGenerator(neurons.length);
		for (int i = 0; i < n.length; i++) {
			neurons[i] = new myNeurons();
			neurons[i].pos = pGen.getNextFreePoint();
			neurons[i].content = n[i];

			int type = sChrome.getGene(i).getType();
			// color of neuron depends on type
			if (type == SomaGene.INPUT)
				neurons[i].color = new Color(0x2222EE);
			if (type == SomaGene.HIDDEN)
				neurons[i].color = new Color(0x22EE22);
			if (type == SomaGene.OUTPUT)
				neurons[i].color = new Color(0xEE2222);
			if (type == SomaGene.BIAS)
				neurons[i].color = new Color(0xEEEE22);

		}

		for (int i = 0; i < a.length; i++) {
			axons[i] = new myAxons();
			axons[i].content = a[i];
		}

		// link every somawrapper with its axonwrapper
		for (int i = 0; i < n.length; i++) {
			int nID = sChrome.getGene(i).getID();

			for (int k = 0; k < a.length; k++) {
				int startID = aChrome.getGene(k).getStartID();
				int endID = aChrome.getGene(k).getEndID();
				if (startID == nID) {
					axons[k].start = neurons[i].pos;
				}
				if (endID == nID) {
					axons[k].end = neurons[i].pos;
				}
			}
		}

	}

	public class PointGenerator {
		int size;
		int step;

		public PointGenerator(int n) {
			size = n;
			step = 0;
		}

		public Point getNextFreePoint() {
			step++;
			return new Point(Math.random() / 2 - 0.25, Math.random() / 2 - 0.25, Math.random() / 2 - 0.25);
		}
	}

	public class Point {
		public Point(double x, double y, double z) {
			this.x = x;
			this.y = y;
			this.z = z;
		}

		double getX() {
			return x;
		}

		double getY() {
			return y;
		}

		private double x, y, z;

		@Override
		public String toString() {
			return "(" + x + "," + y + ")";
		}
	}

	public class myNeurons {
		Point pos;
		Neuron content;
		Color color;
	}

	public class myAxons {
		Point start, end;
		Axon content;

		@Override
		public String toString() {
			return "Start: " + start + " End: " + end + " Weight: " + content.getWeight();
		}
	}

	@Override
	public void step(SimState state) {

		BufferStrategy bf = this.getBufferStrategy();
		Graphics g = null;
		try {
			g = bf.getDrawGraphics();
			g.setColor(Color.white);
			((Graphics2D)g).setComposite( AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                                           0.7f));
			((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                       RenderingHints.VALUE_ANTIALIAS_ON);

			g.clearRect(0, 0, getWidth(), getHeight());

			drawAxons(g);
			drawNeurons(g);
		} finally {
			// It is best to dispose() a Graphics object when done with it.
			g.dispose();
		}

		// Shows the contents of the backbuffer on the screen.
		bf.show();

		// Tell the System to do the Drawing now, otherwise it can take a few
		// extra ms until
		// Drawing is done which looks very jerky
		//Toolkit.getDefaultToolkit().sync();
	}
	
	@Override
	public void paint(Graphics g) {
		super.paint(g);
		step(null);
	}

	private void drawAxons(Graphics g) {
		Graphics2D g2 = (Graphics2D) g;

		boolean fail = false;
		for (int i = 0; i < this.axons.length; i++) {
			myAxons a = axons[i];
			if (a.content.getMode())
				if (a.content.getWeight() < 0) {
					g.setColor(new Color(0xDD2222));
				} else {
					g.setColor(new Color(0x22DDDD));
				}
			else if (a.content.getWeight() < 0) {
				g.setColor(new Color(0x22DD22));
			} else {
				g.setColor(new Color(0xDD22DD));
			}

			if (a.start != null && a.end != null) {

				float thick = (float)(10 * Math.abs(a.content.getWeight()) / Axon.getMaxWeight());
				g2.setStroke(new BasicStroke(thick));
				g.drawLine(toX(a.start.x), toY(a.start.y), toX(a.end.x), toY(a.end.y));
			} else {
				System.out.println("Axon not set: " + a);
				fail = true;
			}
		}

		g2.setStroke(new BasicStroke(1));
		if (fail)
			System.out.println(brain);

	}

	private void drawNeurons(Graphics g) {
		for (int i = 0; i < this.neurons.length; i++) {
			myNeurons n = neurons[i];
			g.setColor(n.color);
			int width = toXNoOffset(0.05);
			int height = toYNoOffset(0.05);
			//n.content.getSoma().getOutput();
			g.fillOval(toX(n.pos.x) - width / 2, toY(n.pos.y) - height / 2, width, height);
		}

	}

	private void rotate(double angle_x, double angle_y, double angle_z) {
		for (int i = 0; i < neurons.length; i++) {
			Point p = neurons[i].pos;
			double px = p.x;
			double py = p.y;
			double pz = p.z;

			// Rotation um x-Achse
			p.y = py * Math.cos(angle_x) - pz * Math.sin(angle_x);
			p.z = py * Math.sin(angle_x) + pz * Math.cos(angle_x);

			py = p.y;
			pz = p.z;

			// Rotation um y-Achse
			p.x = px * Math.cos(angle_y) + pz * Math.sin(angle_y);
			p.z = -px * Math.sin(angle_y) + pz * Math.cos(angle_y);

			px = p.x;

			// Rotation um z-Achse
			p.x = px * Math.cos(angle_z) - py * Math.sin(angle_z);
			p.y = py * Math.cos(angle_z) + px * Math.sin(angle_z);

		}

	}

	private int toX(double x) {
		return (int) ((x + 0.5) * (getWidth() - 50) + 25);
	}

	private int toXNoOffset(double x) {
		return (int) (x * (getWidth()));
	}

	private int toY(double y) {
		return (int) ((y + 0.5) * (getHeight() - 50) + 25);
	}

	private int toYNoOffset(double y) {
		return (int) ((y) * (getHeight()));
	}

	@Override
	public void windowActivated(WindowEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void windowClosed(WindowEvent e) {
		shutdown();
	}

	private void shutdown() {
		if (stoppable != null)
			stoppable.stop();
	}

	@Override
	public void windowClosing(WindowEvent e) {
		shutdown();

	}

	@Override
	public void windowDeactivated(WindowEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void windowDeiconified(WindowEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void windowIconified(WindowEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void windowOpened(WindowEvent e) {
		// TODO Auto-generated method stub

	}

	int mouseX = -1, mouseY = -1;

	@Override
	public void mouseDragged(MouseEvent e) {
		if (mouseX == -1) {
			mouseX = e.getX();
			mouseY = e.getY();
		}
		int xDiff = mouseX - e.getX();
		int yDiff = mouseY - e.getY();
		mouseX = e.getX();
		mouseY = e.getY();
		rotate(0.01 * xDiff, 0.01 * yDiff, 0);
		step(null);
	}

	@Override
	public void mouseMoved(MouseEvent e) {

	}

	@Override
	public void mouseClicked(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mousePressed(MouseEvent e) {

	}

	@Override
	public void mouseReleased(MouseEvent e) {
		mouseX = -1;
		mouseY = -1;

	}

}
